Crate async_ffi

source ·
Expand description

FFI-compatible Futures

Rust currently doesn’t provide stable ABI nor stable layout of related structs like dyn Future or Waker. With this crate, we can wrap async blocks or async functions to make a Future FFI-safe.

FfiFuture provides the same functionality as Box<dyn Future<Output = T> + Send> but it’s FFI-compatible, aka. repr(C). Any Future<Output = T> + Send + 'static can be converted into FfiFuture by calling into_ffi on it, after useing the trait FutureExt.

FfiFuture implements Future<Output = T> + Send. You can await a FfiFuture just like a normal Future to wait and get the output.

For non-Send or non-'static futures, see the section Variants of FfiFuture below.

Examples

Provide some async functions in library: (plugin side)

// Compile with `crate-type = ["cdylib"]`.
use async_ffi::{FfiFuture, FutureExt};

#[no_mangle]
pub extern "C" fn work(arg: u32) -> FfiFuture<u32> {
    async move {
        let ret = do_some_io(arg).await;
        do_some_sleep(42).await;
        ret
    }
    .into_ffi()
}

Execute async functions from external library: (host or executor side)

use async_ffi::{FfiFuture, FutureExt};

// #[link(name = "myplugin...")]
extern "C" {
    #[no_mangle]
    fn work(arg: u32) -> FfiFuture<u32>;
}

async fn run_work(arg: u32) -> u32 {
    unsafe { work(arg).await }
}

Proc-macro helpers

If you enable the feature macros (disabled by default), an attribute-like procedural macro async_ffi is available at top-level. See its own documentation for details.

With the macro, the example above can be simplified to:

use async_ffi::async_ffi;

#[no_mangle]
#[async_ffi]
pub async extern "C" fn work(arg: u32) -> u32 {
    let ret = do_some_io(arg).await;
    do_some_sleep(42).await;
    ret
}

Panics

You should know that unwinding across an FFI boundary is Undefined Behaviour.

Panic in Future::poll

Since the body of async fn is translated to Future::poll by the compiler, the poll method is likely to panic. If this happen, the wrapped FfiFuture will catch unwinding with std::panic::catch_unwind, returning FfiPoll::Panicked to cross the FFI boundary. And the other side (usually the plugin host) will get this value in the implementation of <FfiFuture<T> as std::future::Future>::poll, and explicit propagate the panic, just like std::sync::Mutex’s poisoning mechanism.

Panic in Future::drop or any waker vtable functions Waker::*

Unfortunately, this is very difficult to handle since drop cleanup and Waker functions are expected to be infallible. If these functions panic, we would just call std::process::abort to terminate the whole program.

Variants of FfiFuture

There are a few variants of FfiFuture. The table below shows their corresponding std type.

TypeThe corresponding std type
FfiFuture<T>Box<dyn Future<Output = T> + Send + 'static>
LocalFfiFuture<T>Box<dyn Future<Output = T> + 'static>
BorrowingFfiFuture<'a, T>Box<dyn Future<Output = T> + Send + 'a>
LocalBorrowingFfiFuture<'a, T>Box<dyn Future<Output = T> + 'a>

All of these variants are ABI-compatible to each other, since lifetimes and Send cannot be represented by the C ABI. These bounds are only checked in the Rust side. It’s your duty to guarantee that the Send and lifetime bounds are respected in the foreign code of your external fns.

Performance and cost

The conversion between FfiFuture and orinary Future is not cost-free. Currently FfiFuture::new and its alias FutureExt::into_ffi does one extra allocation. When polling an FfiFuture, the Waker supplied does one extra allocation when cloned.

It’s recommended to only wrap you async code once right at the FFI boundary, and use ordinary Future everywhere else. It’s usually not a good idea to use FfiFuture in methods, trait methods, or generic codes.

abi-stable support

If you want to use this crate with abi-stable interfaces. You can enable the feature flag abi_stable (disabled by default), then the struct FfiFuture and friends would derive abi_stable::StableAbi.

Structs

Enums

Constants

  • The ABI version of FfiFuture and all variants. Every non-compatible ABI change will increase this number, as well as the crate major version.

Traits

Type Definitions

  • The FFI compatible future type with Send bound and 'static lifetime, which is needed for most use cases.
  • The FFI compatible future type without Send bound but with 'static lifetime.

Attribute Macros

  • async_ffimacros
    A helper macro attribute to converts an async fn into a ordinary fn returning FfiFuture.